﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using UBCon.Core.Commands;

namespace UBCon.Navigation
{
    internal class NumericUpDown:Control
    {
        #region Constructors

        static NumericUpDown() 
        {
            Type ownerType = typeof(NumericUpDown);
            NumericUpDown.DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
        }

        public NumericUpDown() : 
            base() 
        {
            IncrementCommand = new Command(OnUpDownCommandExecuted);
            DecrementCommand = new Command(OnUpDownCommandExecuted);
            Value = MinValue;
            Loaded += NumericUpDown_Loaded;
        }

        #endregion

        #region Properties

        [Bindable(true),
        ReadOnly(false), 
        Browsable(true),
        Category("Behaviour")]
        public int MinValue
        {
            get { return (int)GetValue(MinValueProperty); }
            set { SetValue(MinValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinValueProperty =
            DependencyProperty.Register("MinValue", typeof(int), typeof(NumericUpDown),
            new FrameworkPropertyMetadata(0, new PropertyChangedCallback(OnMinValuePropertyChanged)));

        [Bindable(true),
        ReadOnly(false),
        Browsable(true),
        Category("Behaviour")]
        public int MaxValue
        {
            get { return (int)GetValue(MaxValueProperty); }
            set { SetValue(MaxValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxValueProperty =
            DependencyProperty.Register("MaxValue", typeof(int), typeof(NumericUpDown),
            new FrameworkPropertyMetadata(100, new PropertyChangedCallback(OnMaxValuePropertyChanged)));

        [Bindable(true),
        ReadOnly(false),
        Browsable(true),
        Category("Behaviour")]
        public int Value
        {
            get { return (int)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown),
            new FrameworkPropertyMetadata(0, new PropertyChangedCallback(OnValuePropertyChanged), new CoerceValueCallback(CoerceValueProperty)));

        public int IncrementBy
        {
            get { return (int)GetValue(IncrementByProperty); }
            set { SetValue(IncrementByProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IncrementBy.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IncrementByProperty =
            DependencyProperty.Register("IncrementBy", typeof(int), typeof(NumericUpDown), new FrameworkPropertyMetadata(1));

        internal Command IncrementCommand
        {
            get { return (Command)GetValue(IncrementCommandProperty); }
            set { SetValue(IncrementCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IncrementCommand.  This enables animation, styling, binding, etc...
        internal static readonly DependencyProperty IncrementCommandProperty =
            DependencyProperty.Register("IncrementCommand", typeof(Command), typeof(NumericUpDown));

        internal Command DecrementCommand
        {
            get { return (Command)GetValue(DecrementCommandProperty); }
            set { SetValue(DecrementCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DecrementCommand.  This enables animation, styling, binding, etc...
        internal static readonly DependencyProperty DecrementCommandProperty =
            DependencyProperty.Register("DecrementCommand", typeof(Command), typeof(NumericUpDown));

        public int Delay
        {
            get { return (int)GetValue(DelayProperty); }
            set { SetValue(DelayProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Delay.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DelayProperty =
            DependencyProperty.Register("Delay", typeof(int), typeof(NumericUpDown), new FrameworkPropertyMetadata(200));

        public string StringFormat
        {
            get { return (string)GetValue(StringFormatProperty); }
            set { SetValue(StringFormatProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StringFormat.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StringFormatProperty =
            DependencyProperty.Register("StringFormat", typeof(string), typeof(NumericUpDown), new FrameworkPropertyMetadata(null));
        
        #endregion

        #region Depenency Property Changed Callbacks

        private static void OnMinValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
        {
            NumericUpDown numeric = sender as NumericUpDown;
            int newValue = (int)e.NewValue;
            
            if (newValue > numeric.Value
                || !numeric.IsLoaded) 
            {
                numeric.Value = newValue;
            }
        }

        private static void OnMaxValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
        {
            NumericUpDown numeric = sender as NumericUpDown;
            
            int newValue = (int)e.NewValue;
            
            if (newValue < numeric.MinValue) 
            {
                throw new InvalidOperationException("MaxValue is less than MinValue");
            }
        }

        private static object CoerceValueProperty(DependencyObject sender, object value) 
        {
            int pendingValue = (int)value;
            NumericUpDown numeric = sender as NumericUpDown;
            if (pendingValue <= numeric.MinValue) 
            {
                return numeric.MinValue;
            }
            else if (pendingValue >= numeric.MaxValue) 
            {
                return numeric.MaxValue;
            }
            return value;
        }

        private static void OnValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
        {
            NumericUpDown numeric = sender as NumericUpDown;
            int newValue = (int)e.NewValue, oldValue = (int)e.OldValue;
            numeric.UpdateExecution(newValue);
            ValueChangedEventArgs args = new ValueChangedEventArgs(ValueChangedEvent) { MinValue = numeric.MinValue, MaxValue = numeric.MaxValue, NewValue = newValue, OldValue = oldValue };
            numeric.RaiseEvent(args);
        }

        #endregion

        #region Overrides

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (textBox != null)
            {
                textBox.PreviewKeyDown -= textBox_KeyDown;
            }
            
            textBox = GetTemplateChild(textBoxName) as TextBox;
            
            if (textBox != null) 
            {
                textBox.PreviewKeyDown += textBox_KeyDown;
            }
        }

        #endregion

        #region Private Utilities

        private void UpdateExecution(int value) 
        {
            if (value == MinValue) 
            {
                DecrementCommand.CanExecute = false;
                
                if (MaxValue != MinValue)
                {
                    IncrementCommand.CanExecute = true;
                }
            }
            else if (value == MaxValue)
            {
                IncrementCommand.CanExecute = false;
                
                if (MaxValue != MinValue) 
                {
                    DecrementCommand.CanExecute = true;
                }
            }
            else 
            {
                DecrementCommand.CanExecute = true;
                IncrementCommand.CanExecute = true;
            }

        }

        private void OnUpDownCommandExecuted(object parameter) 
        {
            if (parameter == null) 
            {
                return;
            }

            string p = (string)parameter;

            if (string.Compare(p, "Up") == 0) 
            {
                Value += IncrementBy;
                SelectText();
                
            }
            else if (string.Compare(p, "Down") == 0)
            {
                Value -= IncrementBy;
                SelectText();
            }
        }

        private void SelectText() 
        {
            if (textBox != null) 
            {
                if (!textBox.IsFocused)
                {
                    textBox.Focus();
                }
                textBox.SelectAll();
            }
        }
        #endregion

        #region Event Handlers

        private void NumericUpDown_Loaded(object sender, RoutedEventArgs e)
        {
            Loaded -= NumericUpDown_Loaded;
            UpdateExecution(Value);
        }

        private void textBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Up)
            {
                OnUpDownCommandExecuted("Up");
            }
            else if (e.Key == Key.Down)
            {
                OnUpDownCommandExecuted("Down");
            }
        }

        #endregion

        #region Fields

        private const string textBoxName = "PART_TextBox";
        private TextBox textBox;
        #endregion

        #region Events

        public event ValueChangedEventHandler ValueChanged 
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        public static RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent
            (
                "ValueChanged",
                 RoutingStrategy.Bubble,
                 typeof(ValueChangedEventHandler),
                 typeof(NumericUpDown)
            );
        #endregion
    }
}
